///////////////////////////////////////////////////////////////////////////////
//                                                                           //
// class for logging debug, info and error messages                          //
//                                                                           //
// The CpLog class is a singleton class. It allows to steer the output       //
// level and output streams for different types of messages via static       //
// methods.                                                                  //
//                                                                           //
// It also handles the messages produces by the preprocessor macros defined  //
// in the header file: CpDebug, CpInfo, CpWarning, CpError, CpFatal.         //
//                                                                           //
///////////////////////////////////////////////////////////////////////////////

#include <cstdlib>
#include <strings.h>
#include <Riostream.h>
#include <TError.h>
#include <TNamed.h>
#include <TSystem.h>
#include <TEnv.h>
#include <TArrayC.h>
#include <Varargs.h> // platform independent definition of va_copy

#include "CpLog.h"

ClassImp(CpLog)

// implementation of a singleton here
CpLog *CpLog::fgInstance = NULL;

Bool_t CpLog::fgDebugEnabled = kTRUE;

/**
 * get root logger singleton instance
 */
CpLog *CpLog::GetRootLogger()
{
   if (fgInstance == NULL) {
      // creating singleton
      fgInstance =  new CpLog;
   }

   return fgInstance;
}

/**
 * delete the root logger singleton instance
 */
void CpLog::DeleteRootLogger()
{
   if (fgInstance != NULL) {
      delete fgInstance;
      fgInstance = NULL;
   }
}

/**
 * default private constructor
 */
CpLog::CpLog() :
   TObject(),
   fGlobalLogLevel(kInfo),
   fModuleDebugLevels(),
   fClassDebugLevels(),
   fPrintRepetitions(kTRUE),
   fRepetitions(0),
   fLastType(0),
   fLastMessage(),
   fLastModule(),
   fLastClassName(),
   fLastFunction(),
   fLastFile(),
   fLastLine(0)
{
// default constructor: set default values

   for (Int_t iType = kFatal; iType < kMaxType; iType++) {
      fOutputTypes[iType] = 0;
      fFileNames[iType] = "";
      fOutputFiles[iType] = NULL;
      fOutputStreams[iType] = NULL;
      fCallBacks[iType] = NULL;

      fPrintType[iType] = kTRUE;
      fPrintModule[iType] = kFALSE;
      fPrintScope[iType] = kTRUE;
      fPrintLocation[iType] = (iType == kDebug);
   }

   // TO BE REVIEWED
   // replace the previous instance by this one
   if (fgInstance) delete fgInstance;

   fgInstance = this;

   SetHandleRootMessages(kTRUE);

   // read the .rootrc settings
   ReadEnvSettings();
}

/**
 * private destructor
 */
CpLog::~CpLog()
{
// destructor: clean up and reset instance pointer

   if (fRepetitions > 0) PrintRepetitions();

   for (Int_t i = 0; i < fModuleDebugLevels.GetEntriesFast(); i++) {
      if (fModuleDebugLevels[i]) fModuleDebugLevels[i]->Delete();
   }

   fClassDebugLevels.Delete();

   for (Int_t i = 0; i < fClassDebugLevels.GetEntriesFast(); i++) {
      if (fClassDebugLevels[i]) fClassDebugLevels[i]->Delete();
   }

   fClassDebugLevels.Delete();

   for (Int_t iType = kFatal; iType < kMaxType; iType++) {
      CloseFile(iType);
   }

   fflush(stderr);
   fflush(stdout);

   fgInstance = NULL;
}

// NOT IMPLEMENTED!?
//_____________________________________________________________________________
CpLog::CpLog(const CpLog &log) :
   TObject(log),
   fGlobalLogLevel(log.fGlobalLogLevel),
   fModuleDebugLevels(log.fModuleDebugLevels),
   fClassDebugLevels(log.fClassDebugLevels),
   fPrintRepetitions(log.fPrintRepetitions),
   fRepetitions(log.fRepetitions),
   fLastType(log.fLastType),
   fLastMessage(log.fLastMessage),
   fLastModule(log.fLastModule),
   fLastClassName(log.fLastClassName),
   fLastFunction(log.fLastFunction),
   fLastFile(log.fLastFile),
   fLastLine(log.fLastLine)
{
// copy constructor

   Fatal("CpLog", "copy constructor not implemented");
}

// NOT IMPLEMENTED!?
//_____________________________________________________________________________
CpLog &CpLog::operator = (const CpLog& /*log*/)
{
// assignment operator

   Fatal("operator =", "assignment operator not implemented");
   return *this;
}


/**
 * gSystem see TSystem.h
 * gEnv see TEnv.h
 *
 * LOG_NO_DEBUG: fgDebugEnabled <- false
 * ColdPig.CpLog.EnableDebug
 * ColdPig.CpLog.GlobalLogLevel
 */
//_____________________________________________________________________________
void CpLog::ReadEnvSettings()
{
// load settings from the root configuration file (.rootrc)
// and from environment variables

   static const char *typeNames[kMaxType] = {"kFatal", "kError", "kWarning", "kInfo", "kDebug"};

   // debug en- or disabling
   if (gSystem->Getenv("LOG_NO_DEBUG")) {
      fgDebugEnabled = kFALSE;
   } else if (gEnv->Defined("ColdPig.CpLog.EnableDebug")) {
      fgDebugEnabled = gEnv->GetValue("ColdPig.CpLog.EnableDebug", fgDebugEnabled);
      CpInfo(Form("debug %sabled", ((fgDebugEnabled) ? "en" : "dis")));
   }

   // global log level
   if (gEnv->Defined("ColdPig.CpLog.GlobalLogLevel")) {
      const char *type = gEnv->GetValue("ColdPig.CpLog.GlobalLogLevel", "");

      for (Int_t iType = kFatal; iType < kMaxType; iType++) {
         if (strcmp(type, typeNames[iType]) == 0) fGlobalLogLevel = iType;
      }

      CpDebug(3, Form("global log level set to %d", fGlobalLogLevel));
   }

   // global debug level
   if (gEnv->Defined("ColdPig.CpLog.GlobalDebugLevel")) {
      Int_t level = gEnv->GetValue("ColdPig.CpLog.GlobalDebugLevel", Int_t(fGlobalLogLevel - kDebugOffset));

      if (level < -kDebugOffset) level = kDebugOffset;

      fGlobalLogLevel = kDebugOffset + level;
      CpDebug(3, Form("global debug level set to %d", fGlobalLogLevel - kDebugOffset));
   }

   // module debug level
   if (gEnv->Defined("ColdPig.CpLog.ModuleDebugLevel")) {
      TString levels = gEnv->GetValue("ColdPig.CpLog.ModuleDebugLevel", "");
      char *p = const_cast<char *>(levels.Data());

      while (const char *module = strtok(p, " ")) {
         p = NULL;
         char *pos = const_cast<char *>(index(module, ':'));

         if (!pos) continue;

         *(pos++) = '\0';
         Int_t level = atoi(pos);
         SetModuleDebugLevel(module, level);
         CpDebug(3, Form("debug level for module %s set to %d", module, level));
      }
   }

   // class debug level
   if (gEnv->Defined("ColdPig.CpLog.ClassDebugLevel")) {
      TString levels = gEnv->GetValue("ColdPig.CpLog.ClassDebugLevel", "");
      char *p = const_cast<char *>(levels.Data());

      while (const char *className = strtok(p, " ")) {
         p = NULL;
         char *pos = const_cast<char *>(index(className, ':'));

         if (!pos) continue;

         *(pos++) = '\0';
         Int_t level = atoi(pos);
         SetClassDebugLevel(className, level);
         CpDebug(3, Form("debug level for class %s set to %d", className, level));
      }
   }

   // general output stream
   if (gEnv->Defined("ColdPig.CpLog.Output")) {
      TString stream = gEnv->GetValue("ColdPig.CpLog.Output", "Standard");

      if (stream.CompareTo("standard", TString::kIgnoreCase) == 0) {
         SetStandardOutput();
         CpDebug(3, "output stream set to standard output for all types");
      } else if (stream.CompareTo("error", TString::kIgnoreCase) == 0) {
         SetErrorOutput();
         CpDebug(3, "output stream set to error output for all types");
      } else if (!stream.IsNull()) {
         SetFileOutput(stream);
         CpDebug(3, Form("output stream set to file %s for all types", stream.Data()));
      }
   }

   // individual output streams
   for (Int_t iType = kFatal; iType < kMaxType; iType++) {
      TString name("ColdPig.CpLog.Output.");
      name += &typeNames[iType][1];

      if (gEnv->Defined(name)) {
         TString stream = gEnv->GetValue(name, "Standard");

         if (stream.CompareTo("standard", TString::kIgnoreCase) == 0) {
            SetStandardOutput(EType_t(iType));
            CpDebug(3, Form("output stream set to standard output for type %s", typeNames[iType]));
         } else if (stream.CompareTo("error", TString::kIgnoreCase) == 0) {
            SetErrorOutput(EType_t(iType));
            CpDebug(3, Form("output stream set to error output for type %s", typeNames[iType]));
         } else if (!stream.IsNull()) {
            SetFileOutput(EType_t(iType), stream);
            CpDebug(3, Form("output stream set to file %s for type %s", stream.Data(), typeNames[iType]));
         }
      }
   }

   // handling of root error messages
   if (gEnv->Defined("ColdPig.CpLog.HandleRootMessages")) {
      Bool_t on = gEnv->GetValue("ColdPig.CpLog.HandleRootMessages", kTRUE);
      SetHandleRootMessages(on);
      CpDebug(3, Form("handling of root messages %sabled", ((on) ? "en" : "dis")));
   }

   // printout settings
   static const char *settingNames[4] = {"Type", "Module", "Scope", "Location"};
   Bool_t *settings[] = {fPrintType, fPrintModule, fPrintScope, fPrintLocation};

   for (Int_t iSetting = 0; iSetting < 4; iSetting++) {
      TString name("ColdPig.CpLog.Print");
      name += settingNames[iSetting];

      if (gEnv->Defined(name)) {
         Bool_t on = gEnv->GetValue(name, settings[iSetting][0]);

         for (Int_t iType = kFatal; iType < kMaxType; iType++) {
            settings[iSetting][iType] = on;
         }

         CpDebug(3, Form("printing of %s %sabled for all types", settingNames[iSetting], ((on) ? "en" : "dis")));
      }

      for (Int_t iType = kFatal; iType < kMaxType; iType++) {
         TString nameType = name + "." + &typeNames[iType][1];

         if (gEnv->Defined(nameType)) {
            Bool_t on = gEnv->GetValue(nameType, settings[iSetting][iType]);
            settings[iSetting][iType] = on;
            CpDebug(3, Form("printing of %s %sabled for type %s", settingNames[iSetting], ((on) ? "en" : "dis"), typeNames[iType]));
         }
      }
   }

   // repetition of messages
   if (gEnv->Defined("ColdPig.CpLog.PrintRepetitions")) {
      Bool_t on = gEnv->GetValue("ColdPig.CpLog.PrintRepetitions", kTRUE);
      fPrintRepetitions = on;
      CpDebug(3, Form("printing of message repetitions %sabled", ((on) ? "en" : "dis")));
   }
}


//_____________________________________________________________________________
void CpLog::RootErrorHandler(Int_t level, Bool_t abort,
                             const char *location, const char *message)
{
// new error handler for messages from root

   switch (level) {
      case ::kFatal    : level = kFatal; break;
      case ::kSysError :
         DefaultErrorHandler(level, abort, location, message);
         return;
      case ::kBreak    :
         DefaultErrorHandler(level, abort, location, message);
         return;
      case ::kError    : level = kError; break;
      case ::kWarning  : level = kWarning; break;
      case ::kInfo     : level = kInfo; break;
      default          : level = kDebug; break;
   }

   CpLog::Message(level, message, "ROOT", NULL, location, NULL, 0);
}


// DEPRECATED: USE A CONFIGURATION FILE INSTEAD
//_____________________________________________________________________________
void CpLog::EnableDebug(Bool_t enabled)
{
// enable or disable debug output

   fgDebugEnabled = enabled;
}

//_____________________________________________________________________________
void CpLog::SetGlobalLogLevel(EType_t type)
{
// set the global debug level

   // TO BE DELETED
   if (!fgInstance) new CpLog;

   fgInstance->fGlobalLogLevel = type;
}

//_____________________________________________________________________________
Int_t CpLog::GetGlobalLogLevel()
{
// get the global debug level

   if (!fgInstance) new CpLog;

   return fgInstance->fGlobalLogLevel;
}

//_____________________________________________________________________________
void CpLog::SetGlobalDebugLevel(Int_t level)
{
// set the global debug level

   if (!fgInstance) new CpLog;

   if (level < -kDebugOffset) level = -kDebugOffset;

   fgInstance->fGlobalLogLevel = kDebugOffset + level;
}

//_____________________________________________________________________________
Int_t CpLog::GetGlobalDebugLevel()
{
// get the global debug level

   if (!fgInstance) new CpLog;

   return fgInstance->fGlobalLogLevel - kDebugOffset;
}

//_____________________________________________________________________________
void CpLog::SetModuleDebugLevel(const char *module, Int_t level)
{
// set the debug level for the given module

   if (!module) return;

   if (!fgInstance) new CpLog;

   TObject *obj = fgInstance->fModuleDebugLevels.FindObject(module);

   if (!obj) {
      obj = new TNamed(module, module);
      fgInstance->fModuleDebugLevels.Add(obj);
   }

   level += kDebugOffset;

   if (level < kFatal) level = kFatal;

   obj->SetUniqueID(level);
}

//_____________________________________________________________________________
void CpLog::ClearModuleDebugLevel(const char *module)
{
// remove the setting of the debug level for the given module

   if (!module) return;

   if (!fgInstance) new CpLog;

   TObject *obj = fgInstance->fModuleDebugLevels.FindObject(module);

   if (obj) delete fgInstance->fModuleDebugLevels.Remove(obj);
}

//_____________________________________________________________________________
void CpLog::SetClassDebugLevel(const char *className, Int_t level)
{
// set the debug level for the given class

   if (!className) return;

   if (!fgInstance) new CpLog;

   TObject *obj = fgInstance->fClassDebugLevels.FindObject(className);

   if (!obj) {
      obj = new TNamed(className, className);
      fgInstance->fClassDebugLevels.Add(obj);
   }

   level += kDebugOffset;

   if (level < kFatal) level = kFatal;

   obj->SetUniqueID(level);
}

//_____________________________________________________________________________
void CpLog::ClearClassDebugLevel(const char *className)
{
// remove the setting of the debug level for the given class

   if (!className) return;

   if (!fgInstance) new CpLog;

   TObject *obj = fgInstance->fClassDebugLevels.FindObject(className);

   if (obj) delete fgInstance->fClassDebugLevels.Remove(obj);
}


//_____________________________________________________________________________
void CpLog::SetStandardOutput()
{
// write all log messages to the standard output (stdout)

   if (!fgInstance) new CpLog;

   for (Int_t iType = kFatal; iType < kMaxType; iType++) {
      fgInstance->CloseFile(iType);
      fgInstance->fOutputTypes[iType] = 0;
   }
}

//_____________________________________________________________________________
void CpLog::SetStandardOutput(EType_t type)
{
// write log messages of the given type to the standard output (stdout)

   if ((type < kFatal) || (type >= kMaxType)) return;

   if (!fgInstance) new CpLog;

   fgInstance->CloseFile(type);
   fgInstance->fOutputTypes[type] = 0;
}

//_____________________________________________________________________________
void CpLog::SetErrorOutput()
{
// write all log messages to the error output (stderr)

   if (!fgInstance) new CpLog;

   for (Int_t iType = kFatal; iType < kMaxType; iType++) {
      fgInstance->CloseFile(iType);
      fgInstance->fOutputTypes[iType] = 1;
   }
}

//_____________________________________________________________________________
void CpLog::SetErrorOutput(EType_t type)
{
// write log messages of the given type to the error output (stderr)

   if ((type < kFatal) || (type >= kMaxType)) return;

   if (!fgInstance) new CpLog;

   fgInstance->CloseFile(type);
   fgInstance->fOutputTypes[type] = 1;
}

//_____________________________________________________________________________
void CpLog::SetFileOutput(const char *fileName)
{
// write all log messages to the given file

   if (!fgInstance) new CpLog;

   for (Int_t iType = kFatal; iType < kMaxType; iType++) {
      if ((fgInstance->fOutputTypes[iType] == 2) &&
          (fgInstance->fFileNames[iType].CompareTo(fileName) != 0)) {
         fgInstance->CloseFile(iType);
      }

      fgInstance->fOutputTypes[iType] = 2;
      fgInstance->fFileNames[iType] = fileName;
      fgInstance->fOutputFiles[iType] = NULL;
      fgInstance->fOutputStreams[iType] = NULL;
   }
}

//_____________________________________________________________________________
void CpLog::SetFileOutput(EType_t type, const char *fileName)
{
// write log messages of the given type to the given file

   if ((type < kFatal) || (type >= kMaxType)) return;

   if (!fgInstance) new CpLog;

   if ((fgInstance->fOutputTypes[type] == 2) &&
       (fgInstance->fFileNames[type].CompareTo(fileName) != 0)) {
      fgInstance->CloseFile(type);
   }

   fgInstance->fOutputTypes[type] = 2;
   fgInstance->fFileNames[type] = fileName;
   fgInstance->fOutputFiles[type] = NULL;
   fgInstance->fOutputStreams[type] = NULL;
}

//_____________________________________________________________________________
void CpLog::CloseFile(Int_t type)
{
// close the file for the given type if needed

   if ((fOutputTypes[type] == 2) && fOutputFiles[type]) {
      Bool_t closeFile = kTRUE;

      for (Int_t iType = kFatal; iType < kMaxType; iType++) {
         if ((iType != type) && (fOutputFiles[iType] == fOutputFiles[type])) {
            closeFile = kFALSE;
         }
      }

      if (closeFile) {
         fclose(fOutputFiles[type]);
         ofstream *stream = reinterpret_cast<ofstream *>(fOutputStreams[type]);
         stream->close();
         delete fOutputStreams[type];
      }
   }

   fOutputFiles[type] = NULL;
   fOutputStreams[type] = NULL;
   fFileNames[type] = "";
   fOutputTypes[type] = 0;
}

//_____________________________________________________________________________
FILE *CpLog::GetOutputStream(Int_t type)
{
// get the output stream for the given type of messages

   if (type > kDebug) type = kDebug;

   if (fOutputTypes[type] == 0) return stdout;
   else if (fOutputTypes[type] == 1) return stderr;
   else if (fOutputTypes[type] == 2) {
      if (!fOutputFiles[type]) {
         FILE *file = NULL;
         ostream *stream = NULL;

         if (!fFileNames[type].IsNull()) {
            for (Int_t iType = kFatal; iType < kMaxType; iType++) {
               if ((iType != type) &&
                   (fFileNames[iType].CompareTo(fFileNames[type]) == 0) &&
                   fOutputFiles[iType]) {
                  file = fOutputFiles[iType];
                  stream = fOutputStreams[iType];
                  break;
               }
            }

            if (!file) {
               file = fopen(fFileNames[type], "a");
               stream = new ofstream(fFileNames[type], ios::app);
            }
         }

         fOutputFiles[type] = file;
         fOutputStreams[type] = stream;

         if (!file) CloseFile(type);
      }

      if (fOutputFiles[type]) return fOutputFiles[type];
   }

   return stdout;
}

//_____________________________________________________________________________
void CpLog::Flush()
{
// flush the output streams

   if (!fgInstance) new CpLog;

   for (Int_t iType = kFatal; iType < kMaxType; iType++) {
      if (fgInstance->fOutputFiles[iType]) {
         fflush(fgInstance->fOutputFiles[iType]);
         fgInstance->fOutputStreams[iType]->flush();
      }
   }

   fflush(stderr);
   fflush(stdout);
}


//_____________________________________________________________________________
void CpLog::SetHandleRootMessages(Bool_t on)
{
// enable or disable the handling of messages form root

   if (!fgInstance) new CpLog;

   if (on) {
      SetErrorHandler(RootErrorHandler);
   } else {
      SetErrorHandler(DefaultErrorHandler);
   }
}


//_____________________________________________________________________________
void CpLog::SetPrintType(Bool_t on)
{
// switch on or off the printing of the message type for all message types

   if (!fgInstance) new CpLog;

   for (Int_t iType = kFatal; iType < kMaxType; iType++) {
      fgInstance->fPrintType[iType] = on;
   }
}

//_____________________________________________________________________________
void CpLog::SetPrintType(EType_t type, Bool_t on)
{
// switch on or off the printing of the message type for the given message type

   if ((type < kFatal) || (type >= kMaxType)) return;

   if (!fgInstance) new CpLog;

   fgInstance->fPrintType[type] = on;
}

//_____________________________________________________________________________
void CpLog::SetPrintModule(Bool_t on)
{
// switch on or off the printing of the module for all message types

   if (!fgInstance) new CpLog;

   for (Int_t iType = kFatal; iType < kMaxType; iType++) {
      fgInstance->fPrintModule[iType] = on;
   }
}

//_____________________________________________________________________________
void CpLog::SetPrintModule(EType_t type, Bool_t on)
{
// switch on or off the printing of the module for the given message type

   if ((type < kFatal) || (type >= kMaxType)) return;

   if (!fgInstance) new CpLog;

   fgInstance->fPrintModule[type] = on;
}

//_____________________________________________________________________________
void CpLog::SetPrintScope(Bool_t on)
{
// switch on or off the printing of the scope/class name for all message types

   if (!fgInstance) new CpLog;

   for (Int_t iType = kFatal; iType < kMaxType; iType++) {
      fgInstance->fPrintScope[iType] = on;
   }
}

//_____________________________________________________________________________
void CpLog::SetPrintScope(EType_t type, Bool_t on)
{
// switch on or off the printing of the scope/class name
// for the given message type

   if ((type < kFatal) || (type >= kMaxType)) return;

   if (!fgInstance) new CpLog;

   fgInstance->fPrintScope[type] = on;
}

//_____________________________________________________________________________
void CpLog::SetPrintLocation(Bool_t on)
{
// switch on or off the printing of the file name and line number
// for all message types

   if (!fgInstance) new CpLog;

   for (Int_t iType = kFatal; iType < kMaxType; iType++) {
      fgInstance->fPrintLocation[iType] = on;
   }
}

//_____________________________________________________________________________
void CpLog::SetPrintLocation(EType_t type, Bool_t on)
{
// switch on or off the printing of the file name and line number
// for the given message type

   if ((type < kFatal) || (type >= kMaxType)) return;

   if (!fgInstance) new CpLog;

   fgInstance->fPrintLocation[type] = on;
}


//_____________________________________________________________________________
void CpLog::SetPrintRepetitions(Bool_t on)
{
// switch on or off the printing of the number of repetitions of a message
// instead of repeating the same message

   if (!fgInstance) new CpLog;

   if (!on && (fgInstance->fRepetitions > 0)) fgInstance->PrintRepetitions();

   fgInstance->fPrintRepetitions = on;
}


//_____________________________________________________________________________
void CpLog::WriteToFile(const char *name, Int_t option)
{
// write the log object with the given name and option to the current file

   if (!fgInstance) new CpLog;

   fgInstance->TObject::Write(name, option);
}


//_____________________________________________________________________________
UInt_t CpLog::GetLogLevel(const char *module, const char *className) const
{
// get the logging level for the given module and class

   if (!fgInstance) new CpLog;

   if (className) {
      TObject *obj = fgInstance->fClassDebugLevels.FindObject(className);

      if (obj) return obj->GetUniqueID();
   }

   if (module) {
      TObject *obj = fgInstance->fModuleDebugLevels.FindObject(module);

      if (obj) return obj->GetUniqueID();
   }

   return fgInstance->fGlobalLogLevel;
}

//_____________________________________________________________________________
Int_t CpLog::GetDebugLevel(const char *module, const char *className)
{
// get the debug level for the given module and class

   if (!fgInstance) new CpLog;

   return fgInstance->GetLogLevel(module, className) - kDebugOffset;
}

//_____________________________________________________________________________
void CpLog::PrintMessage(UInt_t type, const char *message,
                         const char *module, const char *className,
                         const char *function, const char *file, Int_t line)
{
// print the given message

   // don't print the message if it is repeated
   if (fPrintRepetitions &&
       (fLastType == type) &&
       (message && (fLastMessage.CompareTo(message) == 0)) &&
       ((module && (fLastModule.CompareTo(module) == 0)) ||
        (!module && fLastModule.IsNull())) &&
       ((className && (fLastClassName.CompareTo(className) == 0)) ||
        (!className && fLastClassName.IsNull())) &&
       ((function && (fLastFunction.CompareTo(function) == 0)) ||
        (!function && fLastFunction.IsNull())) &&
       ((file && (fLastFile.CompareTo(file) == 0)) ||
        (!file && fLastFile.IsNull())) &&
       (fLastLine == line)) {
      fRepetitions++;
      return;
   }

   // print number of repetitions
   if (fRepetitions > 0) PrintRepetitions();

   // remember this message
   fRepetitions = 0;
   fLastType = type;
   fLastMessage = message;
   fLastModule = module;
   fLastClassName = className;
   fLastFunction = function;
   fLastFile = file;
   fLastLine = line;

   // print the message
   FILE *stream = GetOutputStream(type);
   static const char *typeNames[kMaxType] =
   {"Fatal", "Error", "Warning", "Info", "Debug"};

   if (fPrintType[type]) {
      PrintString(type, stream, "%c-", typeNames[type][0]);
   }

   if (fPrintModule[type] && module) {
      PrintString(type, stream, "%s/", module);
   }

   if (fPrintScope[type] && className) {
      PrintString(type, stream, "%s::", className);
   }

   if (message) {
      PrintString(type, stream, "%s: %s", function, message);
   } else {
      PrintString(type, stream, "%s", function);
   }

   if (fPrintLocation[type] && file) {
      PrintString(type, stream, " (%s:%.0d)", file, line);
   }

   if (message) {
      PrintString(type, stream, "\n");
   } else {
      PrintString(type, stream, ": ");
   }

   if (fCallBacks[type])(*(fCallBacks[type]))((EType_t)type, NULL);
}

//_____________________________________________________________________________
void CpLog::PrintRepetitions()
{
// print number of repetitions

   PrintString(fLastType, GetOutputStream(fLastType), " <message repeated %d time%s>\n",
               fRepetitions, (fRepetitions > 1) ? "s" : "");

   if (fCallBacks[fLastType])(*(fCallBacks[fLastType]))((EType_t)fLastType, NULL);
}

//_____________________________________________________________________________
void CpLog::Message(UInt_t level, const char *message,
                    const char *module, const char *className,
                    const char *function, const char *file, Int_t line)
{
// print a log message

   if (!fgInstance) new CpLog;

   // get the message type
   UInt_t type = level;

   if (type >= kMaxType) type = kMaxType - 1;

   // print the message if the debug level allows
   if (level <= fgInstance->GetLogLevel(module, className)) {
      fgInstance->PrintMessage(type, message,
                               module, className, function, file, line);
   }

   // abort in case of a fatal message
   if (type == kFatal) {
      delete fgInstance;

      if (gSystem) {
         gSystem->StackTrace();
         gSystem->Abort();
      } else {
         ::abort();
      }
   }
}

//_____________________________________________________________________________
void CpLog::Debug(UInt_t level, const char *message,
                  const char *module, const char *className,
                  const char *function, const char *file, Int_t line)
{
// print a debug message

   if (level == 0) level = 1;

   level += kDebugOffset;
   Message(level, message, module, className, function, file, line);
}


//_____________________________________________________________________________
Int_t CpLog::RedirectStdoutTo(EType_t type, UInt_t level, const char *module,
                              const char *className, const char *function,
                              const char *file, Int_t line, Bool_t print)
{
// redirect the standard output to the stream of the given type

   if (!fgInstance) new CpLog;

   return fgInstance->RedirectTo(stdout, type, level, module, className,
                                 function, file, line, print);
}

//_____________________________________________________________________________
Int_t CpLog::RedirectStderrTo(EType_t type, UInt_t level, const char *module,
                              const char *className, const char *function,
                              const char *file, Int_t line, Bool_t print)
{
// redirect the standard error output to the stream of the given type

   if (!fgInstance) new CpLog;

   return fgInstance->RedirectTo(stderr, type, level, module, className,
                                 function, file, line, print);
}

//_____________________________________________________________________________
Int_t CpLog::RedirectTo(FILE *stream, EType_t type, UInt_t level,
                        const char *module, const char *className,
                        const char *function, const char *file, Int_t line,
                        Bool_t print)
{
// redirect the standard (error) output stream to the stream of the given type

   // get the original file descriptor to be able to restore it later
   Int_t original = dup(fileno(stream));
   fflush(stream);

   // flush the stream of the selected type
   FILE *newStream = GetOutputStream(type);
   fflush(newStream);
   FILE *tmpfiletmp;

   // redirect stream
   if ((type == kDebug) && (level > 0)) level--;

   if (type + level > GetLogLevel(module, className)) { // /dev/null
      tmpfiletmp = freopen("/dev/null", "a", stream);
   } else if (fOutputTypes[type] == 0) {         // stdout
      if (stream != stdout) dup2(fileno(stdout), fileno(stream));
   } else if (fOutputTypes[type] == 1) {         // stderr
      if (stream != stderr) dup2(fileno(stderr), fileno(stream));
   } else if (fOutputTypes[type] == 2) {         // file
      tmpfiletmp = freopen(fFileNames[type], "a", stream);
   } else if (fOutputTypes[type] == 3) {         // external C++ stream
      // redirection is not possible for external C++ streams
   }

   if (tmpfiletmp) {}

   // print information
   if (print) {
      PrintMessage(type, NULL, module, className, function, file, line);
      fflush(newStream);
   }

   return original;
}

//_____________________________________________________________________________
void CpLog::RestoreStdout(Int_t original)
{
// restore the standard output

   fflush(stdout);
   dup2(original, fileno(stdout));
   close(original);
}

//_____________________________________________________________________________
void CpLog::RestoreStderr(Int_t original)
{
// restore the standard error output

   fflush(stderr);
   dup2(original, fileno(stderr));
   close(original);
}


//_____________________________________________________________________________
ostream &CpLog::Stream(EType_t type, UInt_t level,
                       const char *module, const char *className,
                       const char *function, const char *file, Int_t line)
{
// get the stream object for the given output type

   if (!fgInstance) new CpLog;

   return fgInstance->GetStream(type, level, module, className,
                                function, file, line);
}

//_____________________________________________________________________________
ostream &CpLog::GetStream(EType_t type, UInt_t level,
                          const char *module, const char *className,
                          const char *function, const char *file, Int_t line)
{
// get the stream object for the given output type

   if ((type == kDebug) && (level > 0)) level--;

   Bool_t noOutput = (type + level > GetLogLevel(module, className));

   if (!noOutput) {
      PrintMessage(type, NULL, module, className, function, file, line);
   }

   fflush(GetOutputStream(type));

   static ofstream nullStream("/dev/null");

   if (noOutput) {
      return nullStream;
   } else if (fOutputTypes[type] == 0) {
      return cout;
   } else if (fOutputTypes[type] == 1) {
      return cerr;
   } else if (fOutputTypes[type] == 2) {
      return *fOutputStreams[type];
   } else if (fOutputTypes[type] == 3) {
      return *fOutputStreams[type];
   }

   return nullStream;
}

void  CpLog::SetStreamOutput(ostream *stream)
{
   // set an external stream as target for log messages of all types
   // the external stream is completely handled by the caller, the
   // CpLog class just writes to it

   for (Int_t iType = kFatal; iType < kMaxType; iType++) {
      SetStreamOutput((CpLog::EType_t)iType, stream);
   }
}

void  CpLog::SetStreamOutput(EType_t type, ostream *stream)
{
   // set an external stream as target for log messages of the given type
   // the external stream is completely handled by the caller, the
   // CpLog class just writes to it

   if ((type < kFatal) || (type >= kMaxType)) return;

   if (!fgInstance) new CpLog;

   if (fgInstance->fOutputTypes[type] == 2) {
      fgInstance->CloseFile(type);
   }

   fgInstance->fOutputTypes[type] = 3;
   fgInstance->fFileNames[type] = "";
   fgInstance->fOutputFiles[type] = NULL;
   fgInstance->fOutputStreams[type] = stream;
}

void  CpLog::SetLogNotification(CpLogNotification pCallBack)
{
   // set a notification callback function for log messages of all types

   for (Int_t iType = kFatal; iType < kMaxType; iType++) {
      SetLogNotification((CpLog::EType_t)iType, pCallBack);
   }
}

void  CpLog::SetLogNotification(EType_t type, CpLogNotification pCallBack)
{
   // set a notifications call back function for log messages of all types
   // the callback fuction is invoced whenever an output was written
   // Note: does not work for c++ streamer classes, the external stream
   // has to handle this diectly (e.g. custom implementation of endl)

   if ((type < kFatal) || (type >= kMaxType)) return;

   if (!fgInstance) new CpLog;

   fgInstance->fCallBacks[type] = pCallBack;
}

void  CpLog::PrintString(Int_t type, FILE *stream, const char *format, ...)
{
   // this is the general method to print a log message using variadac args
   // to the FILE* like (C - like) streams, e.g. stdout, stderr, or files
   // opened by fopen.
   // Only in case of an external c++ ostream type output, the message is
   // written to that stream and the notifictaion callback is called.
   // The message is printed by a normal vfprintf function otherwise

   if (format == NULL) return;

   va_list ap;
   va_start(ap, format);

   if (fOutputTypes[type] != 3) {
      if (stream != NULL) {
         vfprintf(stream, format, ap);
      }
   } else {
      // build the string and write everthing to the corresponding ostream
      TString fmt(format);
      TArrayC tgt(fmt.Length() * 10); // just take a number
#ifdef R__VA_COPY
      va_list bap;
      R__VA_COPY(bap, ap);
#else
#warning definition of R__VA_COPY has disappeared
#endif //R__VA_COPY

      Int_t iResult = 0;

      while (1) {
         iResult = vsnprintf(tgt.GetArray(), tgt.GetSize(), format, ap);

         if (iResult == -1) {
            iResult = tgt.GetSize() * 2;
         } else if (iResult < tgt.GetSize()) {
            break;
         }

#ifdef R__VA_COPY

         if (iResult < 10000) {
            tgt.Set(iResult + 1);
            va_end(ap);
            R__VA_COPY(ap, bap);
         } else
#endif
         {
            //R__VA_COPY
            tgt[tgt.GetSize() - 1] = 0;

            break;
         }
      }

#ifdef R__VA_COPY
      va_end(bap);
#endif //R__VA_COPY

      if (fOutputStreams[type]) {
         *(fOutputStreams[type]) << tgt.GetArray();
      }
   }

   va_end(ap);
}
